//@author A0096466N
//
// **********
//
// searcher class takes in the entire storage of tasks via vector<Task>
// and returns the searched tasks via vector<Task>
//
// possible searches:
//		by taskname
//		by date
//		by interval
//		by priority
//		by status
//
// searcher first tries to search for priority, interval, deadline, status.
// if unable to find any, search by taskname.
//
//
// **********

#include "Searcher.h"

Searcher::Searcher(void) {

}

Searcher::~Searcher(void) {

}

// sets the tolerance on how many typos
// are allowed when searching for taskname
// can be replaced by a estimation function
int Searcher::typoTolerance(int size) {
	return size/4;
}

// calculates the number of moves required
// to transform string 1 into string 2
// (e.g. string1 = abcde, string2 = abdcx)
// c -> d, d -> c, e -> x
// 3 moves, thus the function will return 3
int Searcher::calculateLevenshteinDistance(const string & string1, const string & string2) { 

    
	int m(string1.size());  
    int n(string2.size());

    if(m == 0) {
		return n;
    }
 
    if(n == 0) {
		return m;
    }
    
	int * typos = new int[n+1];   
 
    for(int i = 0; i < n+1; i++) { 
        typos[i] = i;   
	}
    
	int i = 0;  
 
    for(int iter1 = 0; iter1 < m; iter1++, i++) {
        typos[0] = i+1;    
		int corner = i;    
        int j = 0;    
 
        for(int iter2 = 0; iter2 < n; iter2++, j++ ) {
            int upper = typos[j+1];
 
			if(string1[iter1] == string2[iter2]) {
					typos[j+1] = corner;     
            } else {
				int temp(upper<corner ? upper:corner); 
				typos[j+1] = (typos[j]<temp ? typos[j]:temp)+1;  
			}      
            corner = upper;  
		} 
	}  
 
    int levenshteinDistance = typos[n];  
    delete [] typos; 
        
	return levenshteinDistance;
}

// determines whether to search
// via date
// via taskname
// via priority
// via status
vector <Task> Searcher::execute(string toBeSearched, vector<Task> store) {
    ofstream writeFile;
	writeFile.open(FILENAME_LOGGER.c_str(), ofstream::out | ofstream::app);
	writeFile << "Attempting to search" << endl;
	writeFile.close();
	
	vector<Task> forOutputBox;
    Parser parse;

	if(store.size() == NIL) {
		throw exception("No tasks to search from");
	}
	
	if (toBeSearched[0] == SYMBOL_PRIORITY || toBeSearched == HIGH || toBeSearched == MEDIUM ||
		toBeSearched == LOW) {
		forOutputBox = searchPriority(toBeSearched,store);
	
	} else if(toBeSearched.find_first_of(DASH) != string::npos) {
		forOutputBox = searchInterval(toBeSearched,store);

	} else if(toBeSearched.size() >= 3 && (toBeSearched[1] == SLASH || toBeSearched[2] == SLASH)) {
		forOutputBox = searchDeadline(toBeSearched,store);
	
	} else if(toBeSearched == DONE || toBeSearched == CMI || toBeSearched == WIP || toBeSearched == ARCHIVE || toBeSearched == WORK_IN_PROGRESS) {
		forOutputBox = searchStatus(toBeSearched,store);
	}	
	
	if(forOutputBox.size() == NIL) {
		forOutputBox = searchTaskName(toBeSearched,store);
	}

	return forOutputBox;
}

vector<Task> Searcher::searchDeadline(string toBeSearched,vector<Task> store) {
	ofstream writeFile;
	writeFile.open(FILENAME_LOGGER.c_str(), ofstream::out | ofstream::app);
	writeFile << "Attempting to search by deadline" << endl;
	writeFile.close();
	
	if(toBeSearched.size() < 3) {
		throw exception("search date error");
	}
	
	vector<Task> forOutputBox;
		
	if(toBeSearched[1] != SLASH && toBeSearched[2] != SLASH) {
		throw exception("Search by deadline only accepts date format");
	}

	int find = toBeSearched.find(DASH);
	
	if(find == string::npos) {
		for(vector<Task>::iterator iter = store.begin(); iter != store.end(); iter++) {
			if((*iter).getDeadline() == toBeSearched) {
				forOutputBox.push_back(*iter);
			}
		}
	
	} else {			// indicating that the user wants to see a range
		
		int end = toBeSearched.find_first_of(" -");		// finding where first date ends
		string startDate = toBeSearched.substr(0,end);
		end = toBeSearched.find(DASH);
		int start = toBeSearched.find_first_not_of(" -",end);	// finding where second date starts
		string endDate = toBeSearched.substr(start);
		
		string startMonth = startDate.substr(startDate.find(SLASH)+1);	// extract the month
		string startDay = startDate.substr(0,startDate.find(SLASH));
		string endMonth = endDate.substr(endDate.find(SLASH)+1);	// extract the month
		string endDay = endDate.substr(0,endDate.find(SLASH));

		int sMonth = 0;
		int sDay = 0;
		int eMonth = 0;
		int eDay = 0;

		sMonth = atoi(startMonth.c_str());
		sDay = atoi(startDay.c_str());
		eMonth = atoi(endMonth.c_str());
		eDay = atoi(endDay.c_str());

		if(sMonth > 12 || sMonth <1 || sDay > 31 || sDay < 1) {
			forOutputBox.clear();
			return forOutputBox;
		}
		if(eMonth > 12 || eMonth < 1 || eDay > 31 || eDay < 1) {
			forOutputBox.clear();
			return forOutputBox;
		}

		time_t rawtime;
        time(&rawtime);  /* current time in seconds */

		struct tm * currentTime;
 
        currentTime = localtime(&rawtime);
 
        (*currentTime).tm_hour = 0;                             
        (*currentTime).tm_min = 0;
        (*currentTime).tm_sec = 0;
		(*currentTime).tm_mon = sMonth -1;
		(*currentTime).tm_mday = sDay;

        time_t beginDate = mktime(currentTime);					// start date in terms of seconds (time_t)
	
		(*currentTime).tm_hour = 0;                             
        (*currentTime).tm_min = 0;
        (*currentTime).tm_sec = 0;
		(*currentTime).tm_mon = eMonth -1;
		(*currentTime).tm_mday = eDay;

		time_t finishDate = mktime(currentTime);				// end date in terms of seconds (time_t)

		if(finishDate < beginDate) {							// in the situation where finish date is "less than" start date,
			(*currentTime).tm_year += 1;						// assume its the next year.
			finishDate = mktime(currentTime);
		}


		time_t difference = finishDate - beginDate;

		struct tm * currentlySearchingThisDate;
		
		while(difference >= 0) {
			currentlySearchingThisDate = localtime(&beginDate);
			toBeSearched = to_string((*currentlySearchingThisDate).tm_mday);
			toBeSearched += "/" + to_string((*currentlySearchingThisDate).tm_mon + 1);

			for(vector<Task>::iterator iter = store.begin(); iter != store.end(); iter++) {
				if((*iter).getDeadline() == toBeSearched) {
					forOutputBox.push_back(*iter);
				}
			}
			difference -= 60*60*24;
			beginDate += 60*60*24;
		}
		
	
	
	}
	return forOutputBox;
}

//e.g. search christmas - new year
//this function finds the first taskname with christmas
//and the last taskname with new year,
//and subsequently searches for tasks inbetween these two
//tasks.
// ** christmas & new year must be in the store of tasks
vector<Task> Searcher::searchInterval(string toBeSearched,vector<Task> store) {
	ofstream writeFile;
	writeFile.open(FILENAME_LOGGER.c_str(), ofstream::out | ofstream::app);
	writeFile << "Attempting to search by interval" << endl;
	writeFile.close();

	string startDate = EMPTY;
	string endDate = EMPTY;
	vector<Task> forOutputBox;
	
	int find = toBeSearched.find_first_of("-");
	if(find == NIL || find == string::npos) {
		forOutputBox.clear();
		return forOutputBox;
	}
	string startString = toBeSearched.substr(0,find);
	
	//trimming as there may be spaces between the first word and '-'
	while(startString[startString.size()-1] == WHITE_SPACE) {
		if(startString.size() == 1) {
			forOutputBox.clear();
			return forOutputBox;
		}
		startString = startString.substr(0,startString.size()-1);
	}

	find = toBeSearched.find_first_not_of(" -",find);
	if(find == NIL || find == string::npos) {
		forOutputBox.clear();
		return forOutputBox;
	}
	string endString = toBeSearched.substr(find);
	
	 
	if(startString.size() >= 3 && (startString[1] == SLASH || startString[2] == SLASH)) {
		startDate = startString;
	
	// finding the first occurance of the taskname and taking the date
	} else {
		Sorter sort;
	
		forOutputBox = searchTaskName(startString,store);	
		if(forOutputBox.size() > 1) {
			forOutputBox = sort.start(forOutputBox);
		}

		if(forOutputBox.size() != NIL) {
			for(int i = 0; i < forOutputBox.size(); i++) {
				if(forOutputBox[i].getDeadline() != EMPTY) {
					startDate = forOutputBox[i].getDeadline();
					break;
				} 				
			}
			
		} 
	}

	if(startDate == EMPTY) {
		forOutputBox.clear();
		return forOutputBox;
	}
	
	
	if(endString.size() >= 3 && (endString[1] == SLASH || endString[2] == SLASH)) {
		endDate = endString;
	
	// finding the last occurance of the task name and taking the date
	} else {
		forOutputBox = searchTaskName(endString,store);
		Sorter sort;
		if(forOutputBox.size() > 1) {
			forOutputBox = sort.start(forOutputBox);
		}

		if(forOutputBox.size() != NIL) {
			for(int i = forOutputBox.size()-1 ; i >= 0 ; i--) {
				if(forOutputBox[i].getDeadline() != EMPTY) {
					endDate = forOutputBox[i].getDeadline();
					break;
				}
			}
			
		} 
	}
	
	if(endDate == EMPTY) {
		forOutputBox.clear();
		return forOutputBox;
	}

	if(startDate.size() < 3 || endDate.size() < 3) {
		forOutputBox.clear();
		return forOutputBox;
	}
	
	string date = startDate + "-" + endDate;

	try {
		return searchDeadline(date,store);	
	} catch(...) {
		forOutputBox.clear();
		return forOutputBox;
	}
	 return forOutputBox;
}

vector<Task> Searcher::searchPriority(string toBeSearched,vector<Task> store) {
	ofstream writeFile;
	writeFile.open(FILENAME_LOGGER.c_str(), ofstream::out | ofstream::app);
	writeFile << "Attempting to search by priority" << endl;
	writeFile.close();
	
	vector<Task> forOutputBox;

	// remove whitespace & SYMBOL_PRIORITY
	while(toBeSearched[0] == WHITE_SPACE || toBeSearched[0] == SYMBOL_PRIORITY) {
		toBeSearched = toBeSearched.substr(1);
	}

	if(toBeSearched == HIGH_FAST) {
		toBeSearched = HIGH;
	} else if (toBeSearched == MEDIUM_FAST) {
		toBeSearched = MEDIUM;
	} else if (toBeSearched == LOW_FAST) {
		toBeSearched = LOW;
	}

	if(toBeSearched != HIGH && toBeSearched != MEDIUM && toBeSearched != LOW) {
		throw exception("Search priority error");
	}

	for(vector<Task>::iterator iter = store.begin(); iter != store.end(); iter++) {
		if((*iter).getPriority() == toBeSearched) {
			forOutputBox.push_back(*iter);
		}
	}

	return forOutputBox;
}

vector<Task> Searcher::searchStatus(string toBeSearched,vector<Task> store) {
	ofstream writeFile;
	writeFile.open(FILENAME_LOGGER.c_str(), ofstream::out | ofstream::app);
	writeFile << "Attempting to search by status" << endl;
	writeFile.close();
	
	bool isStatus = false;
	if(toBeSearched == DONE || toBeSearched == CMI || toBeSearched == WIP || toBeSearched == ARCHIVE || toBeSearched == WORK_IN_PROGRESS) {
		if(toBeSearched == WORK_IN_PROGRESS) {
			toBeSearched = WIP;
		}
		isStatus = true;
	}

	assert(isStatus);

	vector<Task> forOutputBox;

	for(vector<Task>::iterator iter = store.begin(); iter != store.end(); iter++) {
		if((*iter).getStatus() == toBeSearched) {
			forOutputBox.push_back(*iter);
		}
	}

	return forOutputBox;

}

vector<Task> Searcher::searchTaskName(string toBeSearched,vector<Task> store) {
	ofstream writeFile;
	writeFile.open(FILENAME_LOGGER.c_str(), ofstream::out | ofstream::app);
	writeFile << "Attempting to search by taskname" << endl;
	writeFile.close();

	vector<Task> forOutputBox;
	string partialTaskName;
	string data;
	
	string toBeSearchedLowerCase = toBeSearched;
	for(unsigned int i = 0; i < toBeSearchedLowerCase.size(); i++) {
		toBeSearchedLowerCase[i] = tolower(toBeSearchedLowerCase[i]);
	}

	for(vector<Task>::iterator iter = store.begin(); iter != store.end(); iter++) {
	
		data = (*iter).getTaskName();
		for(unsigned int i = 0; i < data.size(); i++) {
			data[i] = tolower(data[i]);
		}	
		// if using c++ string find function can find, straight away add in,
		// dont need to use levenshtein distance.
		if(data.find(toBeSearchedLowerCase) != string::npos) {
			forOutputBox.push_back(*iter);

		} else {
			
			for(unsigned int i = 0; i < data.size(); i++) {
				partialTaskName = data.substr(i,toBeSearchedLowerCase.size());
					
				if(calculateLevenshteinDistance(toBeSearchedLowerCase,partialTaskName) <= typoTolerance(toBeSearched.size())) {
					forOutputBox.push_back(*iter);
						break;
				}
			}
		}
	}
	return forOutputBox;	
}